﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using DiscoveryLogic.Analytics.DataObjects;
using DiscoveryLogic.Analytics.DataObjects.Visuals;

namespace DiscoveryLogic.Analytics.Diagrams.Physics.Renderers
{
    public class RadialRenderer:IGraphRenderer
    {
        #region Implementation of IGraphRenderer

        public event OnFinishedGraphRendering GraphRenderFinished;
        private int _MaxIteration = 1000;
        private bool _IsStopped;
        /// <summary>
        /// 
        /// </summary>
        public bool IsStopped
        {
            get { return _IsStopped; }
            set { _IsStopped=value; }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="vertices"></param>
        /// <param name="edges"></param>
        /// <param name="constraintRect"></param>
        /// <param name="iteration"></param>
        /// <param name="attractForceConst"></param>
        /// <param name="repellingForceConst"></param>
        public void UpdatePositions(List<Vertice> vertices, List<Edge> edges, Rect constraintRect, int iteration, double attractForceConst, double repellingForceConst)
        {
            if (iteration > this._MaxIteration)
            {
                this.IsStopped = true;
                return;
            }

            Dictionary<Guid,Vertice> vs = new Dictionary<Guid, Vertice>();
            foreach(Vertice v in vertices)
                vs.Add(v.ID, v);
            // only one anchor and it can not move
            Vertice sun = null;
            foreach(Vertice v in vertices)
            {
                if(!v.CanMove)
                {
                    sun = v;
                    break;
                }
            }
            if (sun == null)
                sun = vertices[0];
            sun.Position=new Vector(constraintRect.Width/2,constraintRect.Height/2);
            // determine number of circles 
            Dictionary<int, List<Guid>> satellites = new Dictionary<int, List<Guid>>();
            List<Guid> vIDs = new List<Guid>();
            vIDs.Add(sun.ID);
            satellites.Add(0, vIDs);
            List<Guid> processedIDs=new List<Guid>();
            processedIDs.Add(sun.ID);
            while (processedIDs.Count < vertices.Count)
            {
                int found = 0;
                List<int> levels=new List<int>();
                levels.AddRange(satellites.Keys);
                levels.Sort();
                int innderLevel = levels[levels.Count - 1];
                int currentLevel = innderLevel + 1;
                List<Guid> fromIDs = satellites[innderLevel];
                List<Guid> toIDs=new List<Guid>();
                foreach (Vertice v in vertices)
                {
                    if(processedIDs.Contains(v.ID))
                        continue;

                    foreach(Edge e in edges)
                    {
                        if(fromIDs.Contains(e.VerticeID1) && e.VerticeID2==v.ID)
                        {
                            toIDs.Add(v.ID);
                            break;
                        }
                        else if(fromIDs.Contains(e.VerticeID2) && e.VerticeID1==v.ID)
                        {
                            toIDs.Add(v.ID);
                            break;
                        }
                    }
                }
                found = toIDs.Count;
                if (found > 0)
                {
                    satellites.Add(currentLevel, toIDs);
                    processedIDs.AddRange(toIDs);
                }
                else 
                {
                    List<Guid> outmostSatelites = new List<Guid>();
                    foreach(Vertice v in vertices)
                    {
                        if(!processedIDs.Contains(v.ID))
                            outmostSatelites.Add(v.ID);
                    }
                    satellites.Add(currentLevel, outmostSatelites);
                    processedIDs.AddRange(outmostSatelites);
                }
            }

            double R = Math.Min(constraintRect.Width, constraintRect.Height)/3;
            double r = R/satellites.Count;

            // clock/counter-clock force
            Vector p1 = sun.Position;
            int numOfMoves = 0;

            for(int i=1; i<satellites.Count;i++)
            {
                int innerLevel = i - 1;
                List<Guid> toIDs = satellites[i];
                List<Guid> fromIDs = satellites[innerLevel];
                Dictionary<Guid, double> angleAssignments=new Dictionary<Guid, double>();
                if(innerLevel==0)
                {
                    double deltaAngle = 360.0 / toIDs.Count;
                    for(int k=0;k<toIDs.Count;k++)
                    {
                        angleAssignments.Add(toIDs[k], deltaAngle * k);
                    }
                }
                else
                {
                    foreach(Guid fromID in fromIDs)
                    {
                        double fromAngle = 0;
                        Vector p2 = vs[fromID].Position;
                        if(p1.X==p2.X)
                        {
                            if (p2.Y > p1.Y)
                                fromAngle = 90;
                            else
                                fromAngle = 270;
                        }
                        else if (p1.Y == p2.Y)
                        {
                            if (p2.X > p1.X)
                                fromAngle = 0;
                            else
                                fromAngle = 180;
                        }
                        else
                        {
                            fromAngle = 180.0*Math.Atan(Math.Abs((p2.Y - p1.Y)/(p2.X - p1.X)));
                            if(fromAngle >=0 && fromAngle<90)
                            {
                                
                            }
                            else if(fromAngle >=90 && fromAngle<180)
                            {
                                fromAngle = 180 - fromAngle;
                            }
                            else if(fromAngle >=180 && fromAngle<270)
                            {
                                fromAngle = fromAngle - 180;
                            }
                            else if(fromAngle >=270 && fromAngle < 360)
                            {
                                fromAngle = 360 - fromAngle;
                            }

                            if (p2.X > p1.X && p2.Y > p1.Y)
                            {

                            }
                            else if (p2.X < p1.X && p2.Y > p1.Y)
                            {
                                fromAngle = 180 - fromAngle;
                            }
                            else if (p2.X < p1.X && p2.Y < p1.Y)
                            {
                                fromAngle = 180 + fromAngle;
                            }
                            else
                            {
                                fromAngle = 360 - fromAngle;
                            }
                        }

                        List<Guid> connectedIDs=new List<Guid>();
                        foreach(Edge edge in edges)
                        {
                            if(edge.VerticeID1==fromID && toIDs.Contains(edge.VerticeID2))
                            {
                                if (!connectedIDs.Contains(edge.VerticeID2))
                                    connectedIDs.Add(edge.VerticeID2);
                            }
                            else if(edge.VerticeID2==fromID && toIDs.Contains(edge.VerticeID1))
                            {
                                if (!connectedIDs.Contains(edge.VerticeID1))
                                    connectedIDs.Add(edge.VerticeID1);
                            }
                        }
                        if(connectedIDs.Count>0)
                        {
                            double deltaAngle = 360.0*50.0/(r*i*2*Math.PI);
                            fromAngle = fromAngle - deltaAngle * connectedIDs.Count / 2;
                            for (int k = 0; k < connectedIDs.Count; k++)
                            {
                                if (!angleAssignments.ContainsKey(connectedIDs[k]))
                                    angleAssignments.Add(connectedIDs[k], fromAngle + deltaAngle*k);
                            }
                        }
                        
                    }
                }

                // update position 
                foreach (Guid toID in toIDs)
                {
                    if (!angleAssignments.ContainsKey(toID))
                    {
                        double x = RandomNumberBetween(0,(int) constraintRect.Width);
                        double y = RandomNumberBetween((int)constraintRect.Height + 10, (int)constraintRect.Height + 1000);
                        vs[toID].Position = new Vector(x, y);
                    }
                    else
                    {
                        double ri = i*r;
                        double a1 = angleAssignments[toID];
                        if(a1 <0)
                            a1 = 360 + a1;
                        double x, y;
                        if(a1>=0 && a1<90)
                        {
                            double a2 = a1 / 180 * Math.PI;
                            x = p1.X + ri * Math.Cos(a2);
                            y = p1.Y + ri * Math.Sin(a2);
                        }
                        else if(a1>=90 && a1<180)
                        {
                            a1 = 180 - a1;
                            double a2 = a1 / 180 * Math.PI;
                            x = p1.X - ri * Math.Cos(a2);
                            y = p1.Y + ri * Math.Sin(a2);
                        }
                        else if(a1 >180 && a1<270)
                        {
                            a1 -= 180;
                            double a2 = a1 / 180 * Math.PI;
                            x = p1.X - ri * Math.Cos(a2);
                            y = p1.Y - ri * Math.Sin(a2);
                        }
                        else
                        {
                            a1 =360-a1;
                            double a2 = a1 / 180 * Math.PI;
                            x = p1.X + ri * Math.Cos(a2);
                            y = p1.Y - ri * Math.Sin(a2);
                        }
                        
                        Vector newPos = new Vector(x, y);
                        if ((vs[toID].Position - newPos).Length > 10)
                            numOfMoves++;
                        vs[toID].Position = newPos;
                    }
                }
            }

            if (numOfMoves == 0)
            {
                this.IsStopped = true;
            }
        }

        private int RandomNumberBetween(int low, int high)
        {
            Random rand=new Random();
            int num = rand.Next(high);
            while (num<low)
            {
                num = rand.Next(high);
            }
            return low;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dc"></param>
        /// <param name="netVisual"></param>
        /// <param name="vertices"></param>
        /// <param name="edges"></param>
        public void RenderEdges(
            DrawingContext dc, NetworkVisual netVisual,
            List<Vertice> vertices, List<Edge> edges)
        {
            Dictionary<Guid, Vertice> vs = new Dictionary<Guid, Vertice>();
            foreach (Vertice v in vertices)
            {
                vs.Add(v.ID, v);
            }
            foreach (Edge e in edges)
            {
                Vertice v1 = vs[e.VerticeID1];
                Vertice v2 = vs[e.VerticeID2];
                EdgeVisual ev = null;
                if (netVisual.EdgeVisuals.ContainsKey(v1.Tag.EntityType) &&
                    netVisual.EdgeVisuals[v1.Tag.EntityType].ContainsKey(v2.Tag.EntityType))
                {
                    ev = netVisual.EdgeVisuals[v1.Tag.EntityType][v2.Tag.EntityType];
                }
                Point p1 = v1.Position.ToPoint();
                Point p2 = v2.Position.ToPoint();
                if (v2.Control != null)
                {
                    double d = (v1.Position - v2.Position).Length;
                    double dx = Math.Abs(p1.X - p2.X) / d * v2.Control.Width / 2;
                    double dy = Math.Abs(p1.Y - p2.Y) / d * v2.Control.Height / 2;
                    if (p1.X < p2.X)
                        p2.X -= dx;
                    else
                        p2.X += dx;
                    if (p1.Y < p2.Y)
                        p2.Y -= dy;
                    else
                        p2.Y += dy;
                }
                Color edgeColor = e.PenColor;
                Pen pen = new Pen(new SolidColorBrush(e.PenColor), e.Thickness);
                if (e.LineStyle == EdgeLineStyle.Dashed)
                    pen.DashStyle = DashStyles.Dash;
                if (ev != null)
                {
                    edgeColor = ev.Color;
                    pen.Brush = new SolidColorBrush(ev.Color);
                    if (ev.ThicknessMeasure == EdgeThicknessMeasure.Fixed)
                        pen.Thickness = ev.Thickness;
                    else if (ev.ThicknessMeasure == EdgeThicknessMeasure.Distance)
                    {
                        pen.Thickness = Math.Max(1, 10 - e.Tag.Distance);
                    }
                    else
                    {
                        pen.Thickness = e.Tag.Aggregates;
                    }
                    if (ev.LineStyle == EdgeLineStyle.Dashed)
                        pen.DashStyle = DashStyles.Dash;
                }
                if (v1.Disabled || v2.Disabled)
                    pen.Brush = new SolidColorBrush(Color.FromArgb(50, edgeColor.R, edgeColor.G, edgeColor.B));
                if (e is Arc)
                {
                    this.DrawArrow(dc, pen, p1, p2);
                    //pen.EndLineCap = PenLineCap.Triangle;
                    //dc.DrawLine(pen, p1, p2);
                }
                else
                {
                    pen.EndLineCap = PenLineCap.Round;
                    dc.DrawLine(pen, p1, p2);
                }
            }
        }

        private void DrawArrow(DrawingContext dc, Pen pen, Point p1, Point p2)
        {
            Point[] aptArrowHead = new Point[3];
            aptArrowHead[0] = p2;
            DrawingVector vecLine = new DrawingVector(p2.X - p1.X, p2.Y - p1.Y);// build the line vector
            DrawingVector vecLeft = new DrawingVector(-vecLine[1], vecLine[0]);// build the arrow base vector - normal to the line
            double arrowWidth = 8f;
            double arrowTheta = 0.5f;
            // setup remaining arrow head points
            double lineLength = vecLine.Length;
            double th = arrowWidth / (2.0f * lineLength);
            double ta = arrowWidth / (2.0f * (Math.Tan(arrowTheta / 2.0f)) * lineLength);

            // find the base of the arrow
            Point pBase = new Point(aptArrowHead[0].X + -ta * vecLine[0], aptArrowHead[0].Y + -ta * vecLine[1]); //base of the arrow

            // build the points on the sides of the arrow
            aptArrowHead[1] = new Point(pBase.X + th * vecLeft[0], pBase.Y + th * vecLeft[1]);
            aptArrowHead[2] = new Point(pBase.X + -th * vecLeft[0], pBase.Y + -th * vecLeft[1]);

            dc.DrawLine(pen, p1, pBase); //master line

            dc.DrawLine(pen, aptArrowHead[0], aptArrowHead[1]);
            dc.DrawLine(pen, aptArrowHead[1], aptArrowHead[2]);
            dc.DrawLine(pen, aptArrowHead[2], aptArrowHead[0]);
        }
        #endregion
    }
}
